const http = require('http');
const util = require('util');
const url = require('url');
const zlib = require('zlib');
const fs = require('fs');
const path = require('path');
// const querystirng = require('querystring') ;
const ejs = require('ejs');
// const chalk = require('chalk');
const mime = require('mime');


//设置
const template = fs.readFileSync(path.join(__dirname, './template.html'), 'utf8');


const debug = require('debug')('hello');
const config = require('./config');

let stat = util.promisify(fs.stat);
let readdir = util.promisify(fs.readdir);

//undefined 1 2 3 


class Server{
    //prpos 代表着命令台输入的东西
    constructor(prpos){
        this.config = {...config , ...prpos};
        this.template = template;
    }

    start(){
        let server = http.createServer(this.handleRequest.bind(this));
        server.listen(this.config.port,this.config.host,()=>{
            console.log('zyl server服务器 bug联系8106425')
        });
    }


    sendError(req, res){
    // 返回的状态码设置为404
        res.statusCode = 404;
        // 页面返回文字
        res.end(`404 Not Found`);

        //异常被捕捉
        // this.start();

    }

    sendFile(req, res, statObj, p) {
        /*
        * 这里设置了缓存 防止重定向
        * */

        // if (this.cache(req, res, statObj, p)) {
        //     res.statusCode = 304;
        //     return res.end();
        // }


        // 是范围请求就忽略
        if (this.range(req, res, statObj, p)) return;
        // 设置文件类型头，如果不设置，我们访问一个html文件可能会导致下载
        res.setHeader('Content-Type', mime.getType(p) + ';charset=utf8');
        // 如果是需要压缩则定义gzip转化流，讲文件压缩后输出
        let transform = this.gzip(req, res, statObj, p);
        if (transform) {
            return fs.createReadStream(p).pipe(transform).pipe(res);
        }
        // 如果不是不需要压缩则直接返回文件
        fs.createReadStream(p).pipe(res);
    }



    //这是页面的,
    range(req, res , statObj , pname){

        //获取range的请求头
        let range = req.headers['range'];
        //如果请求头是有范围的则采用 断点续传
        if(range){
            //断点续传
            //获得整个文件的长度个大小
            let [, start, end] = range.match(/bytes=(\d*)-(\d*)/);
            
            start = start ? Number(start) : 0;
            
            end = end ? Number(end) : statObj.size - 1;
            
            res.statusCode = 206;
            
            res.setHeader('Content-Range', `bytes ${start}-${end}/${statObj.size - 1}`);

            //创建一个文件流
            fs.createReadStream(p, {start, end}).pipe(res);
        }else{
            return false;
        }
    
    
    }


    //把页面给压缩, 压缩后的页面能节约服务器资源
    gzip(req, res , statObj, pname){

        //判断请求头是否设置了接受编码
        let encoding = req.headers['accept-encoding'];

        if(encoding){
            //gzip 
            //用正则判断是不是gzip
            if (encoding.match(/\bgzip\b/)) {
                //设置请求头，为压缩的，这样能减少网络传输的流程
                res.setHeader('Content-Encoding', 'gzip');
                return zlib.createGzip();
            }
            // deflate
            if (encoding.match(/\bdeflate\b/)) {
                //设置请求头为deflate
                res.setHeader('Content-Encoding', 'deflate');
                return zlib.createDeflate();
            }

            return false;
        }else{

            return false;
        
        }

    }

    

    //设置页面的缓存，让页面具有缓存能力，能减少对服务器的访问
    cache(req, res, statObj , pname){
        //设置缓存过期时间
        res.setHeader('Cache-Control', 'no-cache');
        //如果没有Cache-Control ， 则去查找Expires
        res.setHeader('Expires', new Date(Date.now() + 10 * 1000).getTime());

        // 设置etag和上次最新修改时间, 设置etag, 
        let eTag = statObj.ctime.getTime() + '-' + statObj.size;

        //设置lastModified , 设置
        let lastModified = statObj.ctime.getTime();
        // 传给客户端
        res.setHeader('Etag', eTag);     
        //lastModified是一个缓存方案，精度比Etag低 备用方案   
        res.setHeader('Last-Modified', lastModified);

        //客户端把上次的设置带过来
        //用反射拿到请求头
        let ifNoneMatch = req.header['if-none-match'];
        let ifModifiedSince  = req.header['if-modified-since'];

        //查看缓存是否失效， 只要缓存丢失
        if(eTag !== ifNoneMatch && lastModified !== ifModifiedSince){
            return false;
        }

        return true;
    }



    async handleRequest(req, res){
        // 获取文件路径
        let {dir, port, host} = this.config;

        //获取请求的路径, 用工具类处理下,处理成为url
        let { pathname } = url.parse(req.url) ;
        if(pathname === '/favicon.ico') {return res.end();}


        //对文件夹的名字进行处理，防止乱码
        pathname = decodeURIComponent(pathname);
        //pname 决定了绝对文件路径
        let pname = path.join(dir, pathname);




        try{
            console.log(pname)
            let statObj = await stat(pname);
            console.log('end')



            //如果路径是文件夹，则index默认访问
            if(statObj.isDirectory()){
                res.setHeader('Content-Type','text/html;charset=utf8');
                
                //readdir 这个方法支持链式调用，就是因为通过util进行了封装下
                let dirs = await readdir(pname);

                //对这个文件下面所有的东西进行遍历 , 返沪一个新的数组
                dirs = dirs.map((item)=>{
                    return{
                        name : item,
                        //把pathname路径拼接上去
                        href:path.join(pathname, item),
                    } 
                });




                //渲染页面， 当前的文件夹
                let str = ejs.render(this.template, {
                    name: `当前页面 ${pathname}`,
                    arr:dirs
                });


                //响应里面返回此页面
                res.end(str);
            //如果路径是文件则直接显示文件
            }else{

                this.sendFile(req, res, statObj, pname);
            }



        }catch(err){
            console.log('异常被捕捉')
            this.sendError(req,res);


            debug(err);
        }


    }

}



module.exports = Server;
